/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package genomemap.worker;

import genomemap.data.DNA_RegionUtil;
import genomemap.data.DataSource;
import genomemap.data.PDataSource;

import javautil.collections.ArrayUtil;
import javautil.io.LogUtil;

import java.util.Random;

/**
 *
 * @author stewari1
 */
public class RandomCost extends AnnealMachine {

    /** Field description */
    private final int RAND_GEBERATOR_MODULAS = 2147483647;

    /** Field description */
    private long randGeneratorSeed = 595337866;

    /** Field description */
    private int[] randMixerBuffer = null;

    /** Field description */
    private Random randomGenerator = null;

    /** Field description */
    private final double delta = 1.0E-6;

    /** Field description */
    private IncrementallyAnnealable incrAnnealable = null;

    /** Field description */
    private int iterationCount = 0;

    /**
     * Constructs ...
     */
    public RandomCost() {
        this(595337866, 128);
    }

    /**
     * Constructs ...
     *
     * @param randGeneratorSeed description
     * @param randMixerBufferSize description
     */
    private RandomCost(long randGeneratorSeed, int randMixerBufferSize) {

        this.randGeneratorSeed = randGeneratorSeed;
        randomGenerator        = new Random(randGeneratorSeed);
        randMixerBuffer        = new int[randMixerBufferSize];

        for (int bufferIndex = 0; bufferIndex < randMixerBufferSize; bufferIndex++) {

            randMixerBuffer[bufferIndex] = randomGenerator.nextInt(RAND_GEBERATOR_MODULAS);
        }
    }

    public static void main(String[] args) {

        int ch_id          = 7;
        int[] order        = {};
        PDataSource pData  = DataSource.getPDataSource(ch_id);
        HammingDistance hd = new HammingDistance(ch_id, HammingDistance.ORDER_CLONES);

        hd.setOrder(ArrayUtil.IntegerSequence(pData.getChData().getCloneCount(), false));

        RandomCost rc = new RandomCost();

        rc.setIncrAnnealable(hd);
        rc.run();
        System.out.println("Converged Order: ");
        LogUtil.print(rc.getConvOrder());

        int[][] hybridData = pData.getHybridData();
        int contigCount    = DNA_RegionUtil.getContigCount(rc.getConvOrder(), hybridData);

        System.out.println("Probe Contig Count(cell Count): " + contigCount);
    }

    /**
     *
     */
    @Override
    public void run() {

        int[] order = incrAnnealable.getOrder();

        /* shuffle the original order */
        int k           = 0,
            l           = 0,
            m           = 0,
            orderLength = order.length;

        for (int i = 0; i < orderLength; i++) {

            k        = (int) (orderLength * superRandom());
            l        = (int) (orderLength * superRandom());
            m        = order[k];
            order[k] = order[l];
            order[l] = m;
        }

        incrAnnealable.setOrder(order);

        /* calculate the initial value */
        double ld     = incrAnnealable.initialValue();
        boolean flag0 = false,
                flag1 = false,
                flag2 = false,
                flag3 = false;
        int n         = 0;
        double ldmin  = 10000000.0;
        double ldmax  = 0.0;
        double tem    = 0.0,
               dld    = 0.0;

        iterationCount = 0;

        for (;;) {

            iterationCount++;

            k = (int) (order.length * (superRandom()));
            l = (int) (order.length * (superRandom()));

            if (k > l) {

                m = k;
                k = l;
                l = m;
            }

            if ((k == 0) && (l == order.length - 1)) {
                continue;
            }

            tem = 4.0 - iterationCount * delta;

            if (tem < 0.0) {
                tem = 0.0;
            }

            dld = incrAnnealable.delta(k, l);

            if (dld + tem * (superRandom() - 0.5) <= 0.0) {

                n++;

                ld += dld;

                if (ld > ldmax) {
                    ldmax = ld;
                }

                if (ld < ldmin) {
                    ldmin = ld;
                }

                for (int j = 0; j < (l - k + 1) / 2; j++) {

                    m            = order[k + j];
                    order[k + j] = order[l - j];
                    order[l - j] = m;
                }

                incrAnnealable.setOrder(order);
            }

            if ((float) iterationCount / 100000 - iterationCount / 100000 < delta) {

                flag3 = flag2;
                flag2 = flag1;
                flag1 = flag0;

                if (ldmax == ldmin) {
                    flag0 = true;
                } else {
                    flag0 = false;
                }


                // force to terminate after a million
                if ((flag0 && flag1 && flag2 && flag3) || (iterationCount > 1000000)) {
                    break;
                }

                ldmin = 10000;
                ldmax = 0;
                n     = 0;
            }
        }

        setConvOrder(order);
        setConvValue(ld);
    }

    /**
     * specially implemented for this class. Uses some private data, for example
     * <code>RAND_GEBERATOR_MODULAS</code>, <code>randGeneratorSeed</code>,
     * <code>randMixerBuffer</code> and <code>randMixerBufferSize</code>
     * @return a value between 0 and 1
     */
    private double superRandom() {

        int k = randMixerBuffer.length
                * (randomGenerator.nextInt(RAND_GEBERATOR_MODULAS) / RAND_GEBERATOR_MODULAS);
        int l = randMixerBuffer[k];

        randMixerBuffer[k] = randomGenerator.nextInt(RAND_GEBERATOR_MODULAS);

        return (double) l / RAND_GEBERATOR_MODULAS;
    }

    public void setIncrAnnealable(IncrementallyAnnealable incrAnnealable) {
        this.incrAnnealable = incrAnnealable;
    }
}
